package com.wq.sofa.framework.rpc.boot.factory;

import com.alipay.sofa.common.profile.StringUtil;
import com.alipay.sofa.rpc.boot.runtime.param.BoltBindingParam;
import com.alipay.sofa.runtime.api.aware.ClientFactoryAware;
import com.alipay.sofa.runtime.api.client.ClientFactory;
import com.alipay.sofa.runtime.api.client.ServiceClient;
import com.alipay.sofa.runtime.api.client.param.BindingParam;
import com.alipay.sofa.runtime.api.client.param.ServiceParam;
import com.wq.sofa.framework.rpc.boot.factory.annotations.SofaRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: wangqiang20995
 * @Date:2018/10/15
 * @Description:
 * @Resource:
 */
@Component
public class SofaServiceExportFactory implements ClientFactoryAware, ApplicationContextAware,ExportRpcService {

    private ClientFactory clientFactory;
    private ApplicationContext applicationContext;

    private Map<Class<?>, Object> serviceRegistry;
    private static final String BASIC_SCAN_PACKAGE = "com.wq.sofa.framework.rpc.boot.service";

    private Logger logger = LoggerFactory.getLogger(getClass());

    public SofaServiceExportFactory() {
        this.serviceRegistry = new ConcurrentHashMap<>();
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
        logger.info("sofa client factory init");
        export();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        logger.info("spring container init");
    }


    public void export() {
        //loadClass to memory first
        try {
            List<Class<?>> scanClass = loadClass();
            logger.info("scan classes in target package --> [{}]",scanClass);
            ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);

            scanClassAndExport(scanClass,serviceClient);
        } catch (ClassNotFoundException e) {
            logger.error(e.getMessage(),e);
        }
    }

    private void scanClassAndExport(List<Class<?>> scanClasses,ServiceClient client){
        if(scanClasses == null || scanClasses.size() == 0){
            return;
        }

        for(Class<?> clazz : scanClasses){
            SofaRpcService sofaRpcService = clazz.getDeclaredAnnotation(SofaRpcService.class);
            if(sofaRpcService == null){
                continue;
            }

            if(doExport(client,sofaRpcService,clazz)){
                //暂时先记录这个信息
                this.serviceRegistry.putIfAbsent(clazz,clazz.getName());
            }
        }
    }

    private boolean doExport(ServiceClient client,SofaRpcService sofaRpcService,Class<?> scanClass){

        ServiceParam serviceParam = null;

        try {
            serviceParam = new ServiceParam();
            serviceParam.setInterfaceType(sofaRpcService.interfaceType());
            serviceParam.setInstance(instance(scanClass));

            //current export service by annotation supports bolt-protocol only
            List<BindingParam> params = new ArrayList<>(1);
            BindingParam serviceBindingParam = new BoltBindingParam();
            params.add(serviceBindingParam);
            serviceParam.setBindingParams(params);

            //do export
            logger.info("export local service [{}]",scanClass.getName());
            client.service(serviceParam);
        } catch (Exception e) {
            logger.error("no default constructor ,so please check and then init it with no arguments." + e.getMessage(),e);
            return false;
        }

        return true;
    }

    private <T> T instance(Class<T> clazz) throws NoSuchMethodException, IllegalAccessException, InstantiationException {
        if(clazz == null){
            throw new NullPointerException("empty class object");
        }

        return clazz.newInstance();
    }

    private List<Class<?>> loadClass() throws ClassNotFoundException {

        List<Class<?>> resourceClasses = null;

        String packagePath = BASIC_SCAN_PACKAGE.replace('.', '/');
        Enumeration<URL> addresses = null;

        try {
            addresses = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            resourceClasses = new ArrayList<>();
            while (addresses.hasMoreElements()) {
                URL url = addresses.nextElement();
                String protocol = url.getProtocol();

                logger.info("protocol is --> " + protocol);

                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    resourceClasses = findAndLoadClassByFile(BASIC_SCAN_PACKAGE,filePath,resourceClasses);
                }else {
                    logger.warn("scan target package for loading classes support file protocol only");
                }
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return resourceClasses;
    }

    private List<Class<?>> findAndLoadClassByFile(String packageName, String filePath, List<Class<?>> loadClass) throws ClassNotFoundException {
        //transfer local file class to JVM Class
        if (StringUtil.isEmpty(filePath) || StringUtil.isEmpty(packageName)) {
            throw new NullPointerException("scan packageName or target filePath must not be empty");
        }

        File direct = new File(filePath);

        if (!direct.exists()) {
            return loadClass;
        }

        File[] dirFiles = direct.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory() || (pathname.getName().endsWith(".class"));
            }
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {
                return findAndLoadClassByFile(packageName + "." + file.getName(), file.getAbsolutePath(), loadClass);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                String fullClassName = packageName + "." + className;
                logger.info("load class --> [{}]", fullClassName);
                loadClass.add(Class.forName(packageName + "." + className));
            }
        }

        return loadClass;
    }

    @Override
    public Map<Class<?>, Object> rpcService() {
        return this.serviceRegistry;
    }
}
